﻿#region Copyright

// // The contents of this file are subject to the Mozilla Public License
// // Version 1.1 (the "License"); you may not use this file except in compliance
// // with the License. You may obtain a copy of the License at
// //   
// // http://www.mozilla.org/MPL/
// //   
// // Software distributed under the License is distributed on an "AS IS"
// // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// // License for the specific language governing rights and limitations under 
// // the License.
// //   
// // The Initial Developer of the Original Code is Robert Smyth.
// // Portions created by Robert Smyth are Copyright (C) 2008-2013.
// //   
// // All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using NDependencyInjection.interfaces;
using NLog;
using NoeticTools.NWorkflow.DotNetInterop;
using NoeticTools.NWorkflow.Exceptions;
using NoeticTools.NWorkflow.ProgressMonitoring;
using NoeticTools.NWorkflow.Scopes;
using NoeticTools.NWorkflow.Steps;
using NoeticTools.NWorkflow.Validation;


namespace NoeticTools.NWorkflow
{
    public class WorkflowRules<TResult> : IWorkflowRules<TResult>
    {
        private readonly Logger _logger;
        private readonly Dictionary<string, IStepRules> _stepsByName = new Dictionary<string, IStepRules>();
        private readonly ISystemDefinition _system;
        private IWorkflowRunner<TResult> _runner;

        protected WorkflowRules(string name, ISystemDefinition system) : this(name)
        {
            _system = system;
        }

        protected WorkflowRules(string name)
        {
            Name = name;
            SyncRoot = new object();
            Progress = new ProgressData();
            OnBegin = new OnBeginEndScope<TResult>(this);
            OnEnd = new OnBeginEndScope<TResult>(this);
            _logger = LogManager.GetLogger("NWorkflow");
        }

        // todo - lifecycle is wrong
        private IWorkflowRunner<TResult> Runner
        {
            get { return _runner; }
            set
            {
                _runner = value;
                OnBegin.Runner = Runner;
                OnEnd.Runner = Runner;
            }
        }

        protected IWorkflow<TResult> Workflow { get; private set; }

        public IStepRules this[string workflowName]
        {
            get
            {
                try
                {
                    return _stepsByName[workflowName];
                }
                catch (Exception exception)
                {
                    var message = string.Format("Exception while resolving step '{0}'.", workflowName);
                    throw new WorkflowConfigurationException(message, exception);
                }
            }
        }

        public IWorkflow<TResult> CreateWorkflow(string name)
        {
            return CreateWorkflow(name, new DotNetTimerFactory());
        }

        public IWorkflow<TResult> CreateWorkflow(string name, ITimerFactory timerFactory)
        {
            var workflow = new Workflow<TResult>(name, this, timerFactory: timerFactory);
            Runner = workflow;
            Workflow = workflow;
            BuildWorkflow();
            return workflow;
        }

        public object SyncRoot { get; private set; }

        public ProgressData Progress { get; private set; }

        public OnBeginEndScope<TResult> OnBegin { get; private set; }

        public OnBeginEndScope<TResult> OnEnd { get; private set; }

        public void TransitionTo(string destinationStep)
        {
            _logger.Trace("Transitioning to step: {0}.", destinationStep);
            Runner.TransitionTo(destinationStep);
        }

        public void Exit(TResult result)
        {
            _logger.Debug("Exit with result {0}.", result);
            Runner.Exit(result);
        }

        public void Stop()
        {
            lock (SyncRoot)
            {
                OnBegin.Stop();
                OnEnd.Run();
            }
        }

        /// <summary>
        ///     Add a variable to this workflow's IoC/DI scope. This defines are variable that can be used by multiple steps in a workflow.
        /// </summary>
        public void AddVariable<T>(T instance)
        {
            _system.HasInstance(instance).Provides<T>();
        }

        public string Name { get; protected set; }

        public void SetStepResult<TStepResult>(TStepResult result)
        {
            lock (SyncRoot)
            {
                Runner.CurrentStep.SetResult(result);
            }
        }

        public void LeaveStep<TStepResult>(TStepResult result)
        {
            var priorStep = Runner.CurrentStep;
            SetStepResult(result);
            if (ReferenceEquals(priorStep, Runner.CurrentStep))
            {
                throw new WorkflowConfigurationException("Leaving a step that is not the current step.");
            }
        }

        internal IEnumerable<IStep> GetSteps()
        {
            var steps = new List<IStep>();
            foreach (var stepRule in _stepsByName.Values)
            {
                steps.Add((IStep)stepRule);
            }
            return steps.ToArray();
        }

        public IStep GetStep(string stepName, ITimer timer)
        {
            try
            {
                return (IStep)_stepsByName[stepName];
            }
            catch (Exception exception)
            {
                throw new WorkflowConfigurationException(string.Format("Problem finding step '{0}'.", stepName), exception);
            }
        }

        public void OnEventFired(object eventValue)
        {
            _logger.Debug("Event fired: {0}.", eventValue);
            Runner.CurrentStep.OnEventFired(eventValue);
        }

        public void Validate()
        {
            var validators = new IValidationRule[]
                {
                    new HasStepsValidationRule(_stepsByName),
                    new OnBeginEndScope<TResult>.HasInitialStepValidationRule(OnBegin),
                };

            foreach (var validator in validators)
            {
                if (!validator.Validate(this))
                {
                    throw new WorkflowConfigurationException();
                }
            }
        }

        protected virtual void BuildWorkflow()
        {
        }

        public Step<TResult, TStepResult> Step<TStepResult>(string stepName)
        {
            var stepRules = (Step<TResult, TStepResult>) _stepsByName[stepName];
            Progress.AddStep(stepRules);
            return stepRules;
        }

        public Step<TResult, TStepResult> AddStep<TStepResult>(string stepName)
        {
            try
            {
                var stepRules = new Step<TResult, TStepResult>(Runner, this, stepName, _system);
                return AddStepRules(stepName, stepRules);
            }
            catch (Exception exception)
            {
                var message = string.Format("Exception while adding step '{0}'.", stepName);
                throw new WorkflowConfigurationException(message, exception);
            }
        }

        private Step<TResult, TStepResult> AddStepRules<TStepResult>(string stepName, Step<TResult, TStepResult> stepRules)
        {
            _stepsByName.Add(stepName, stepRules);
            Progress.AddStep(stepRules);
            return stepRules;
        }

        protected ICompositeStepRulesScope<TResult, TStepResult> AddCompositeStep<TWorkflowRules, TStepResult>(string stepName, ISubsystemBuilder subsystemBuilder)
            where TWorkflowRules : IWorkflowRules<TStepResult>
        {
            var hook = new SubscopeHook();
            _system.HasSubsystem(hook);
            subsystemBuilder.Build(hook.Subsystem);
            return AddCompositeStep(stepName, hook.Subsystem.Get<TWorkflowRules>(), hook.Subsystem);
        }

        protected ICompositeStepRulesScope<TResult, TStepResult> AddCompositeStep<TWorkflowRules, TStepResult>(string stepName)
            where TWorkflowRules : IWorkflowRules<TStepResult>
        {
            return AddCompositeStep(stepName, _system.Get<TWorkflowRules>());
        }

        private ICompositeStepRulesScope<TResult, TStepResult> AddCompositeStep<TStepResult>(string stepName, IWorkflowRules<TStepResult> innerWorkflowRules,
                                                                                             ISystemDefinition system)
        {
            try
            {
                var stepRules = new CompositeStepRules<TResult, TStepResult>(Runner, this, stepName, innerWorkflowRules, system);
                return AddStepRules(stepName, stepRules);
            }
            catch (Exception exception)
            {
                var message = string.Format("Exception while adding workflow '{0}'.", stepName);
                throw new WorkflowConfigurationException(message, exception);
            }
        }

        protected ICompositeStepRulesScope<TResult, TStepResult> AddCompositeStep<TStepResult>(string stepName, IWorkflowRules<TStepResult> innerWorkflowRules)
        {
            return AddCompositeStep(stepName, innerWorkflowRules, _system);
        }

        private class SubscopeHook : ISubsystemBuilder
        {
            public ISystemDefinition Subsystem { get; private set; }

            public void Build(ISystemDefinition system)
            {
                Subsystem = system;
                system.HasInstance(system)
                      .Provides<ISystemDefinition>();
            }
        }
    }
}